# -*- coding: utf-8 -*-

"""
    (c) 2020 - Copyright ...
    
    Authors:
        zPlus <zplus@peers.community>
"""

import datetime
import logging
import pyld
import random
import requests
import string

from . import APP_URL

log = logging.getLogger(__name__)

# List of HTTP headers used by ActivityPub.
# https://www.w3.org/TR/activitypub/#server-to-server-interactions
default_header  = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
optional_header = 'application/activity+json'
headers         = [ default_header, optional_header ]

# Headers to use when GETting/POSTing remote objects
REQUEST_HEADERS = { 'Accept':       default_header,
                    'Content-Type': default_header}

# The JSON-LD context to use with ActivityPub activities.
jsonld_context = [
    'https://www.w3.org/ns/activitystreams',
    'https://w3id.org/security/v1',
    'https://forgefed.peers.community/ns' ]

# Fetch an ActivityPub object
def fetch(uri):
    response = requests.get(uri, headers=REQUEST_HEADERS)
    
    if response.status_code != 200:
        log.info('[{}] Error while retrieving remote object: {}'.format(response.status_code, uri))
        return None
    
    # The remote server is expected to serve a JSON-LD document.
    object = response.json()
    
    # Because JSON-LD can represent the same graph in several different
    # ways, we should normalize the JSONLD object before passing it to the
    # actor for processing. This simplifies working with the object.
    # Normalization could mean "flattening" or "compaction" of the JSONLD
    # document.
    # However, this step is left out for now and not implemented unless
    # needed because the ActivityStream specs already specifies that
    # objects should be served in compact form:
    # https://www.w3.org/TR/social-web-protocols/#content-representation
    # 
    # object = response.json()
    # return normalize(object)
    
    return Document(object)

# Cache a copy of the JSON-LD context such that we can work with activities
# without sending HTTP requests all the time.
cached_jsonld_context = []
for context in jsonld_context:
    cached_jsonld_context.append(requests.get(context, headers=REQUEST_HEADERS).json())

def format_datetime(dt):
    """
    This function is used to format a datetime object into a string that is
    suitable for publishing in Activities.
    """
    
    return dt.replace(microsecond=0) \
             .replace(tzinfo=datetime.timezone.utc) \
             .isoformat()

def new_activity_id(length=32):
    """
    Generate a random string suitable for using as part of an Activity ID.
    """
    
    symbols = string.ascii_lowercase + string.digits
    return ''.join([ random.choice(symbols) for i in range(length) ])

class Document(dict):
    """
    This is a helper class for wrapping JSONLD.
    When we handle remote RDF nodes, for example when receiving Activities
    or fetching remote objects, the JSONLD documents are converted to Python
    dictionaries. The problem is that in JSONLD the value of a property can
    be a scalar, an array, or another object (that represents another node).
    The purpose of this class is to add convenience methods for reading the
    values without checking the type all the times. For example "first()"
    would return the value if it's a scalar, or the first value of an array.
    """
    
    def __init__(self, *args, **kwargs):
      super().__init__(*args, **kwargs)
    
    def first(self, property):
        """
        Return value if it's a scalar, otherwise return the first element if it
        is an array.
        """
        
        if isinstance(self[property], list):
            return self[property][0]
        
        if isinstance(self[property], dict):
            return None
        
        return self[property]
    
    def node(self, property):
        """
        Return the value if it's an object. If it's a string, try to fetch a
        URI.
        """
        
        if isinstance(self[property], dict):
            return Document(self[property])
        
        if isinstance(self[property], str):
            self[property] = fetch(self[property])
            return self.node(property)
        
        return None
